var protocol = global.protocol;
var logger = global.logger;
var Package = protocol.Package;
var Message = protocol.Message;
var usermgr = global.usermgr

var ST_INITED = 0;
var ST_WAIT_ACK = 1;
var ST_WORKING = 2;
var ST_CLOSED = 3;

const handler = require('./handler')
/**
 * Socket class that wraps socket and websocket to provide unified interface for up level.
 */
var Socket = function (id, socket) {
    var self = this;
    this.id = id;
    this.socket = socket;
    this.state = ST_INITED;
    this.timeout = 3000;
    this.account = '';
    // TODO: any other events?
    socket.on('message', function (message) {
        message = Package.decode(message);
        handler(self, message);
    });
};

module.exports = Socket;

/**
 * Send raw byte data.
 *
 * @api private
 */
Socket.prototype.sendRaw = function (msg) {
    if (this.state !== ST_WORKING) {
        return;
    }
    var self = this;

    this.socket.send(msg, { binary: true }, function (err) {
        if (!!err) {
            logger.error('websocket send binary data failed: %j', err.stack);
            return;
        }
    });
};

/**
 * Send byte data package to client.
 *
 * @param  {Buffer} msg byte data
 */
Socket.prototype.send = function (msg) {
    if (msg instanceof String) {
        msg = new Buffer(msg);
    } else if (!(msg instanceof Buffer)) {
        msg = new Buffer(JSON.stringify(msg));
    }

    this.sendRaw(Package.encode(Package.TYPE_DATA, msg));
};

/**
 * Send byte data packages to client in batch.
 *
 * @param  {Buffer} msgs byte data
 */
Socket.prototype.sendBatch = function (msgs) {
    var rs = [];
    for (var i = 0; i < msgs.length; i++) {
        var src = Package.encode(Package.TYPE_DATA, msgs[i]);
        rs.push(src);
    }
    this.sendRaw(Buffer.concat(rs));
};

/**
 * Send message to client no matter whether handshake.
 *
 * @api private
 */
Socket.prototype.sendForce = function (msg) {
    if (this.state === ST_CLOSED) {
        return;
    }
    this.socket.send(msg, { binary: true });
};

/**
 * Response handshake request
 *
 * @api private
 */
Socket.prototype.handshakeResponse = function (resp) {
    if (this.state !== ST_INITED) {
        return;
    }

    this.socket.send(resp, { binary: true });
    this.state = ST_WAIT_ACK;
};

Socket.prototype.heartbeat = function () {
    var self = this;
    if (self.disconnectOnTimeout) {
        clearTimeout(self.disconnectOnTimeout);
        self.disconnectOnTimeout = null;
    }

    self.sendRaw(Package.encode(Package.TYPE_HEARTBEAT));


    self.disconnectOnTimeout = setTimeout(function () {
        self.disconnectOnTimeout = null;
        console.log('client %j heartbeat timeout.', self.id);
        self.disconnect();
    }, self.timeout * 2);
}

/**
 * Close the connection.
 *
 * @api private
 */
Socket.prototype.disconnect = function () {
    if (this.state === ST_CLOSED) {
        return;
    }

    if (this.disconnectOnTimeout) {
        clearTimeout(this.disconnectOnTimeout);
        this.disconnectOnTimeout = null;
    }

    usermgr.del(this.userId);
    this.state = ST_CLOSED;
    this.socket.close();
};

Socket.prototype.kickOff = function (reason) {

    var res = {
        reason: reason
    };
    this.sendRaw(Package.encode(Package.TYPE_KICK, new Buffer(JSON.stringify(res))));


    logger.info(this.account + ' :' + reason)
    //this.disconnect();
}

Socket.prototype.response = function (reqId, route, msg) {
    var compressRoute = 0;
    var type = Message.TYPE_RESPONSE;
    msg = protocol.strencode(JSON.stringify(msg));
    msg = Message.encode(reqId, type, compressRoute, route, msg);
    this.send(msg);
};

Socket.prototype.push = function (route, msg) {
    msg = protocol.strencode(JSON.stringify(msg));
    var compressRoute = 0;
    var type = Message.TYPE_PUSH;
    msg = Message.encode(0, type, compressRoute, route, msg);
    this.send(msg);
};